/****************************************************************************************************

BASIC! is an implementation of the Basic programming language for
Android devices.

This file is part of BASIC! for Android

Copyright (C) 2010 - 2014 Paul Laughton

    BASIC! is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    BASIC! is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with BASIC!.  If not, see <http://www.gnu.org/licenses/>.

    You may contact the author or current maintainers at http://rfobasic.freeforums.org

*************************************************************************************************/

package rfo.mougino.waf;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;

import android.app.Activity;
import android.app.ListActivity;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Typeface;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;


public class Basic extends ListActivity  {

	private static final String LOGTAG = "Basic";
	private static final String CLASSTAG = Basic.class.getSimpleName();

	public static final String SOURCE_DIR    = "source";
	public static final String DATA_DIR      = "data";
	public static final String DATABASES_DIR = "databases";
	public static final String SAMPLES_DIR   = "Sample_Programs";
	public static final String SOURCE_SAMPLES_PATH = SOURCE_DIR + '/' + SAMPLES_DIR;

	public static String AppPath = "waf";				// Set to the path name for application directories
	public static boolean isAPK = false;					// If building APK, set true
	private static boolean apkCreateDataDir;				// If APK needs a /data/ directory, set true
	private static boolean apkCreateDataBaseDir;			// If APK needs a /database/ director, set true

	public static boolean DoAutoRun = false;
	private static String filePath = "";
	private static String basePath = "";

	public static ArrayList<String> lines;					//Program lines for execution

	public static String ProgramFileName;					// Set when program loaded or saved

	public static Context BasicContext;						// saved so we do not have to pass it around
	public static Context theRunContext;

	public static String SD_ProgramPath = "";	// Used by Load/Save
	public static Intent theProgramRunner;

	private Background theBackground;						// Background task ID
	private ArrayAdapter<String> AA;
	private ListView lv;									// The output screen list view
	private ArrayList<String> output;						// The output screen text lines

	public static boolean checkSDCARD(char mount) {			// mount is 'w' for writable,
															// 'r' for either readable or writable
		String state = Environment.getExternalStorageState();
		if (Environment.MEDIA_MOUNTED.equals(state)) { return true; }	// mounted for both read and write
		if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state) && (mount == 'r')) return true;
		return false;
	}

	public static void setFilePaths(String basePath) {		// set both basePath and filePath
															// public so LauncherShortcuts can use it
		if (basePath.equals("none"))
			basePath = Environment.getExternalStorageDirectory().getPath();
		Basic.basePath = basePath;
		Basic.filePath = basePath + File.separatorChar + AppPath;
	}

	public static String getBasePath() {
		return basePath;
	}

	public static String getFilePath() {
		return filePath;
	}

	public static String getFilePath(String subdir, String subPath) {
		// if (!subPath.startsWith("/"))	uncomment to enable absolute paths
		{
			StringBuilder path = new StringBuilder(filePath);
			if (subdir != null) { path.append(File.separatorChar).append(subdir); }
			if (subPath != null) { path.append(File.separatorChar).append(subPath); }
			subPath = path.toString();
		}
		try {
			subPath = new File(subPath).getCanonicalPath();
			if (subPath.length() == 0) { subPath = new File(subPath).getAbsolutePath(); }
		} catch(IOException e) { /* use subPath */ }
		return subPath;
	}

	public static String getSourcePath(String subPath) {
		return getFilePath(SOURCE_DIR, subPath);
	}

	public static String getSamplesPath(String subPath) {
		return getFilePath(SOURCE_SAMPLES_PATH, subPath);
	}

	public static String getDataPath(String subPath) {
		return getFilePath(DATA_DIR, subPath);
	}

	public static String getDataBasePath(String subPath) {
		return getFilePath(DATABASES_DIR, subPath);
	}

	public static String getAppFilePath(String subdir, String subPath) { // path for assets
														// same as getFilePath() but no basePath
		StringBuilder sb = new StringBuilder(AppPath);
		if (subdir != null) { sb.append(File.separatorChar).append(subdir); }
		if (subPath != null) { sb.append(File.separatorChar).append(subPath); }
		String path = sb.toString();								// path may have ".." elements AssetManager can't handle
		File file = new File(path.toString());						// get canonical path to remove them
		try { path = file.getCanonicalPath().substring(1); }		// strip leading '/' from canonical path
		catch (IOException e) { Log.w(LOGTAG, "getAppFilePath - getCanonicalPath: " + e); }
		return path;												// unmodified path if getCanonicalPath threw exception
	}

	private void initVars() {
		// Some of these may not need initialization; if so I choose to err on the side of caution
		BasicContext = getApplicationContext();

		Resources res = getResources();
		AppPath = res.getString(R.string.app_path);
		isAPK   = res.getBoolean(R.bool.is_apk);
		apkCreateDataDir     = res.getBoolean(R.bool.apk_create_data_dir);
		apkCreateDataBaseDir = res.getBoolean(R.bool.apk_create_database_dir);

		DoAutoRun = false;

		ProgramFileName = "";
		theProgramRunner = null;
	}

	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);					// Set up of fresh start
		Log.v(LOGTAG, CLASSTAG + " onCreate");

		Settings.setDefaultValues(this, isAPK);				// if isAPK, force to default settings
		initVars();
		String base = Settings.getBaseDrive(this);
		setFilePaths(base);

		if (isAPK) {
			createForAPK();
		} else {
			createForSB();
		}
	}

	private void createForSB() {							// Create code for Standard Basic

		/* If we have entered Basic and there is a program running, then we should not
		 * interfere with that run. We will just exit this attempt. A program running
		 * is indicated by theRunContext ! = null
		 */
		if (theRunContext != null) {
			finish();
			return;
		}
		InitDirs();											// Initialize Basic directories every time
															// The user may have put in a new SD Card

		clearProgram();										// Clear the basic program

		/* If Basic is entered from a launcher shortcut, then there will an Intent with a
		 * bundle. The bundle will contain the shortcut launcher program name.
		 * Auto run will be called with a new bundle with just the filename.
		 */
		Intent myIntent = getIntent();
		String FileName = myIntent.getStringExtra(LauncherShortcuts.EXTRA_LS_FILENAME); // Launched by shortcut?
		if (FileName == null) {								// Launched with a .bas as argument?
			if (myIntent.getData() != null) FileName = myIntent.getData().getPath();
		}

		if ((FileName != null) && ! FileName.equals("")) {
			Bundle bb = new Bundle();
			bb.putString("fn", FileName);								// fn is the tag for the filename parameter
			Intent intent = new  Intent(BasicContext, AutoRun.class);	// in the bundle going to AutoRun
			intent.putExtras(bb);
			DoAutoRun = true;
			startActivity( intent );
			finish();
		} else if (AreSamplesLoaded()) {								// This is not a launcher short cut
			DoAutoRun = false;
			startActivity(new Intent(BasicContext, Editor.class));		// 	Goto the Editor
			finish();
		} else {														// The sample files have not been loaded
			runBackgroundLoader();							// Start the background task to load samples and graphics
		}
	}

	private void createForAPK() {										// Create code for APK
		runBackgroundLoader();
	}

	private void runBackgroundLoader() {
		// Establish an output screen so that file load progress can be shown.

		output = new ArrayList<String>();
		AA=new ArrayAdapter<String>(this, R.layout.simple_list_layout, output);	// Establish the output screen
		lv = getListView();
		lv.setTextFilterEnabled(false);
		lv.setSelection(0);

		// In order to show progress in the user interface we have to start a background thread to
		// do the actual loading. That background thread will make calls to the UI thread to show
		// show the progress.
		//
		// Once the files are loaded, the background task will start the loaded program running.

		theBackground = new Background();						// Start the background task to load
		theBackground.execute("");								// sample and graphics

	}

	public static void InitDirs(){

		// Initializes (creates) the directories used by Basic

		if (checkSDCARD('w')) {									// Insure that SD card is mounted and writable
			File sdDir;
			if (!isAPK) {
				sdDir = new File(getSourcePath(null));			// source directory
				sdDir.mkdirs();

				sdDir = new File(getSamplesPath(null));			// sample programs directory
				sdDir.mkdirs();

				sdDir = new File(getDataPath(null));			// data directory
				sdDir.mkdirs();

				sdDir = new File(getDataBasePath(null));		// databases directory
				sdDir.mkdirs();
			}

			if (isAPK && apkCreateDataDir) {
				sdDir = new File(getDataPath(null));			// data directory
				sdDir.mkdirs();
			}

			if (isAPK && apkCreateDataBaseDir) {
				sdDir = new File(getDataBasePath(null));		// databases directory
				sdDir.mkdirs();
			}
		}

	}

	public static void clearProgram(){

		lines = new ArrayList<String>();					// The lines array list is the program
		lines.add("");										// add an empty string to lines
		Editor.DisplayText="REM Start of BASIC! Program\n";	// Display text is the editors program storage for display
	}

	private static boolean AreSamplesLoaded(){		// Sample program files have not been loaded
													// if the sample programs directory is empty
		String samplesPath = getSamplesPath(null);
		File sdDir = new File(samplesPath);
		sdDir.mkdirs();
		String FL[] = sdDir.list();

		if ((FL != null) && (FL.length != 0)) {		// if the help directory is not empty
													// then sort the files

			ArrayList<String> FL1 = new ArrayList<String>(Arrays.asList(FL));
			Collections.sort(FL1);					// Convert to array list to easily sort
			String f1 = FL1.get(0);					// The top of the list should be the
			if (f1.length() > 11) {					// f00_vnn.nn_xxx file
				String f2 = f1.substring(5, 7);
				String f3 = f1.substring(8,10);
				f1 = f2 + "." + f3;
				f2 = BasicContext.getString(R.string.version);         // Get the version string
				if (f1.equals(f2)) { return true; }		               // Compare version numbers
			}
			for (String fileName : FL) {			// If different, empty the directory
				File file = new File(samplesPath + File.separatorChar + fileName);
				file.delete();
			}
		}
		return false;
	}

	/******************************* static file/resource utilities ******************************/

	public static String getRawFileName(String input) {
		// Converts a file name to an Android internal resource name.
		// Upper-case characters are converted to lower-case.
		// If there is a dot in the name, the dot and everything after it are dropped.

		// MyFile.png = myfile
		// bigSound.mp3 = bigsound
		// Earth.jpg = earth

		if (input == null) return "";
		String output = input.toLowerCase();		// Convert to lower case
		int index = output.indexOf(".");			// Find the dot
		if (index == -1) return output;				// if no dot, return as is
		return output.substring(0, index);			// else isolate stuff in front of dot
	}

	public static String getAlternateRawFileName(String input) {
		// Converts a file name with upper and lower case characters to a lower case filename.
		// The dot extension is appended to the end of the filename preceeded by "_".
		// Any other dots in the file are also converted to "_".

		// MyFile.png = myfile_png
		// bigSound.mp3 = bigsound_mp3
		// Earth.jpg = earth_jpg
		// Earth.blue.jpg = earth_blue_jpg

		// if there is no dot extension, returns original string

		int idx = input.lastIndexOf("/");
		return idx >= 0 ? input.substring(idx + 1).toLowerCase().replace(".", "_") : input.toLowerCase().replace(".", "_"); // Convert to lower case, convert all '.' to '_'
	}

	public static int getRawResourceID(String fileName) {
		if (fileName == null) fileName = "";
		String packageName = BasicContext.getPackageName();				// Get the package name
		int resID = 0;													// 0 is not a valid resource ID
		for (int attempt = 1; (resID == 0) && (attempt <= 2); ++attempt) {
			String rawFileName =
				(attempt == 1) ? getAlternateRawFileName(fileName) :	// Convert conventional filename to raw resource name, BASIC!-style
				(attempt == 2) ? getRawFileName(fileName) : "";			// If first try didn't work, try again, Android-style.
			if (!rawFileName.equals("")) {
				Resources res = BasicContext.getResources();
				String fullName = packageName + ":raw/" + rawFileName;	// "fully-qualified resource name"
				resID = res.getIdentifier(fullName, null, null);		// Get the resource ID
			}
		}
		return resID;
	}

	public static InputStream streamFromResource(String dir, String path) throws Exception {
		InputStream inputStream = null;
		int resID = getRawResourceID(path);
		if (resID != 0) {
			Resources res = BasicContext.getResources();				// open an input stream from raw resource
			inputStream = res.openRawResource(resID);					// this call may throw NotFoundException
		} else {
			inputStream = BasicContext.getAssets().						// open an input stream from an asset
							open(getAppFilePath(dir, path));			// this call may throw IOException
		}
		return inputStream;
	}

	public static BufferedReader getBufferedReader(String dir, String path) throws Exception {
		File file = new File((dir == null)	? path						// no dir, use path as given
											: getFilePath(dir, path));	// dir is SOURCE_DIR, DATA_DIR, etc
		BufferedReader buf = null;
		if (file.exists()) {
			try {
				buf = new BufferedReader(new FileReader(file));			// open an input stream from the file
				if (buf != null) buf.mark((int)file.length());			// this call may throw an exception
			} catch (IOException ex) {
				Log.e(LOGTAG, "getBufferedReader: " + ex);
				if (buf != null) {
					try { buf.close(); } catch (IOException e) { }		// ignore second exception, if any
				}
				throw ex;												// throw first exception
			}
		} else if (isAPK) {
			InputStream inputStream = streamFromResource(dir, path);
			if (inputStream != null) { buf = new BufferedReader(new InputStreamReader(inputStream)); }
			// TODO: how to mark a non-file buffered stream?
		}
		return buf;
	}

	public static BufferedInputStream getBufferedInputStream(String dir, String path) throws Exception {
		File file = new File((dir == null)	? path						// no dir, use path as given
											: getFilePath(dir, path));	// dir is SOURCE_DIR, DATA_DIR, etc
		BufferedInputStream buf = null;
		if (file.exists()) {
			buf = new BufferedInputStream(new FileInputStream(file));	// open an input stream from the file
			if (buf != null) buf.mark((int)file.length());				// this call can NOT throw any exception
		} else if (isAPK) {
			InputStream inputStream = streamFromResource(dir, path);	// this call may throw an exception
			if (inputStream != null) { buf = new BufferedInputStream(inputStream); }
			// TODO: how to mark a non-file buffered stream?
		}
		return buf;
	}

	public static int loadProgramFileToList(boolean isFullPath, String path, ArrayList<String> list) {

		// MES 2013/01/13 - We got in a jam when moving resources to assets.
		// Files and assets have a different base path. For backward compatibility,
		// we're letting callers use the full path if they only want a file.
		// If a caller wants to use an asset "file", it must set isFullPath false
		// and use a path relative to "source" so getBufferedReader can find the asset.

		int size = 0;
		BufferedReader buf = null;
		try { buf = getBufferedReader((isFullPath ? null : Basic.SOURCE_DIR), path); }
		catch (Exception e) { return size; }

		// Read the file. Insert the the lines into the ArrayList.
		String data = null;
		do {
			try { data = buf.readLine(); }
			catch (IOException e) { data = null; }
			if (data != null) {
				list.add(data);											// add the line
				size += data.length() + 1;
			}
		} while (data != null);											// while not EOF and no error

		if (list.isEmpty()){
			list.add("!");
			size = 2;
		}
		return size;
	}

	public static String loadProgramListToString(ArrayList<String> list, int size) {
		StringBuilder sb = new StringBuilder(size);
		for (String line : list)	 {									// Copy the lines to the String
			sb.append(line + '\n');
		}
		return sb.toString();
	}

																// build program in Basic.lines
	public static void loadProgramFromString(String text, AddProgramLine APL) {
		if (APL == null) { APL = new AddProgramLine(); }
		StringBuilder sb = new StringBuilder();
		int length = text.length();
		int offset = AddProgramLine.charCount;
		for (int k = 0; k < length; ++k) {
			char c = text.charAt(k);							// grab the display text
			if (c == '\n') {
				AddProgramLine.charCount = k + offset;			// and add it to Basic.Lines
				APL.AddLine(sb.toString());						// with some editing
				sb.setLength(0);
			} else {
				sb.append(c);
			}
		}
		if (sb.length() != 0) {									// if last line has no newline
			 APL.AddLine(sb.toString());						// add the line now
		}
	}

	public static IOException closeStreams(InputStream in, OutputStream out) {
		IOException ex = null;
		if (in != null) {
			try { in.close(); }
			catch (IOException e) { ex = e; }
		}
		if (out != null) {
			try {
				out.flush();
				out.close();
			} catch (IOException e) {
				//Log.v(LOGTAG, CLASSTAG + " I/O Exception 4");
				if (ex == null) { ex = e; }
			}
		}
		return ex;
	}

	public static IOException closeStreams(Reader in, Writer out) {
		IOException ex = null;
		if (in != null) {
			try { in.close(); }
			catch (IOException e) { ex = e; }
		}
		if (out != null) {
			try {
				out.flush();
				out.close();
			} catch (IOException e) {
				//Log.v(LOGTAG, CLASSTAG + " I/O Exception 4");
				if (ex == null) { ex = e; }
			}
		}
		return ex;
	}

	/************************************* background loader *************************************/

	// The loading of the sample files and graphics is done in a background AsyncTask rather than the UI task
	// Progress is shown by sending progress messages to the UI task.

	public class Background extends AsyncTask<String, String, String>{

		private String[] mLoadingMsg;						// Displayed while files are loading
		private String mProgressMarker;						// Displayed as a unit of progress while files are loading
		private Resources mRes;

		@Override
		protected void onPreExecute() {
			mRes = BasicContext.getResources();
			mLoadingMsg = mRes.getStringArray(R.array.loading_msg);
			mProgressMarker = mRes.getString(R.string.progress_marker);
			if ((mLoadingMsg != null) && (mLoadingMsg.length != 0)) {
				for (String s : mLoadingMsg) { output.add(s); }			// The message lines that tell what we are doing.
			}
		}

		private final int CHAR_PER_LINE = 20;							// Number of dots per line
		private final int LINES_PER_UPDATE = 30;						// Number of line between Progress Messages
		private int maxUpdateCount = 0;									// Change to maximum number of progress messages, if known.
		private int lineCount = 0;

		@Override
		protected void onProgressUpdate(String... str) {				// Called when publishProgress() is executed.
			for (String s : str) {										// Form line of CHAR_PER_LINE progress characters
				int linenum = output.size() - 1;
				s = output.get(linenum) + s;
				output.set(linenum, s);

				if (s.length() >= CHAR_PER_LINE) {						// After the CHAR_PER_LINEth character
					++lineCount;										// output a new line

					if ((lineCount % LINES_PER_UPDATE) == 0) {			// After every LINES_PER_UPDATE
						StringBuilder msg = new StringBuilder();		// output a progress message

						int updates = lineCount/LINES_PER_UPDATE;
						if (maxUpdateCount != 0) {						// If the max is known
							msg.append((updates * 100)/maxUpdateCount);	// convert progress to a percent
							msg.append("%");
						} else {
							msg.append(updates);						// else just use the number of progress line
						}
						output.add("Continuing load (" + msg + ')');
					}
					output.add("");										// start a new line
				}
			}

			setListAdapter(AA);						// show the output
			lv.setSelection(output.size() - 1);		// set last line as the selected line to scroll
		}

		@Override
		protected String doInBackground(String... str) {
			if (isAPK) {
				doBGforAPK();
			} else {
				doBGforSB();
			}
			return "";
		}

		private void doBGforSB() {								// Background code for Standard Basic
			SD_ProgramPath = SAMPLES_DIR;						// This setting will also force LoadFile to show the samples directory
			if (new File(getFilePath()).exists()) {
				copyAssets(AppPath);
				doFirstLoad();									// First load shows a first load basic program
			} else {
				doCantLoad();									// Can't load: show an error message
			}
			DoAutoRun = false;
			startActivity(new Intent(BasicContext, Editor.class));	// Goto the Editor
			finish();
		}

		private void doBGforAPK() {								// Background code of APK
			InitDirs();											// Initialize Basic directories every time
			LoadGraphicsForAPK();								// Load the sound and graphics files

			lines = new ArrayList <String>();					// Program will be loaded into this array list
			LoadTheProgram();									// Load the basic program into memory

			theProgramRunner = new Intent(BasicContext, Run.class);	// now go run the program
			theRunContext = null;
			DoAutoRun = true;
			startActivity(theProgramRunner);					// The program is now running
			finish();
		}

		private void copyAssets(String path) {	// Recursively copy all the assets in the named subdirectory to the SDCard
			String[] dirs = null;
			try { dirs = getAssets().list(path); }
			catch (IOException e) { Log.e(LOGTAG, "copyAssets: " + e); }
			if (dirs == null) return;

			if (dirs.length == 0) {
				copyAssetToFile(path);
				return;
			}

			new File(getBasePath() + File.separatorChar + path).mkdirs();
			for (String dir : dirs) {
				copyAssets(path + File.separatorChar + dir);
			}
		}

		private void copyAssetToFile(String path) {
			if (path.endsWith(".bas") || path.endsWith(".txt") || path.endsWith(".html")) {
				copyTextAssetToFile(path);
			} else {
				copyBinaryAssetToFile(path);
			}
		}

		private void copyTextAssetToFile(String path) {
			BufferedReader in = null;
			BufferedWriter out = null;

			publishProgress(mProgressMarker);						// Show progress for each program loaded
			File file = new File(getBasePath() + File.separatorChar + path);
			File dir = new File(file.getParent());
			if (!dir.exists()) { dir.mkdirs(); }

			try {
				in = new BufferedReader(new InputStreamReader(getAssets().open(path)));
				out = new BufferedWriter(new FileWriter(file));
				String line;
				while ((line = in.readLine()) != null) {			// Read and write one line at a time
					line +=  "\n";
					out.write(line);
				}
			} catch (IOException e) {
				Log.w(LOGTAG, "copyTextAssetToFile: " + e);
			}
			closeStreams(in,out);
		}

		private void copyBinaryAssetToFile(String path) {
			BufferedInputStream in = null;
			BufferedOutputStream out = null;

			File file = new File(getBasePath() + File.separatorChar + path);
			byte[] bytes = new byte[8192];
			int count = 0;
			try {
				in = new BufferedInputStream(getAssets().open(path));
				out = new BufferedOutputStream(new FileOutputStream(file));
				do {
					count = in.read(bytes, 0, 8192);
					if (count > 0) {
						out.write(bytes, 0, count);
						publishProgress(mProgressMarker);
					}
				} while (count != -1);
			} catch (IOException e) {
				Log.w(LOGTAG, "copyBinaryAssetToFile: " + e);
			}
			closeStreams(in,out);
		}

		private void LoadGraphicsForAPK(){

			// Loads icons and audio to the SDcard.
			// The files to create are listed in setup.xml:load_file_names.
			// The names are relative to "<AppPath>/data/", except that
			// names ending in ".db" are relative to "<AppPath>/databases/".
			// The content is copied from res/raw/ or assets/.

			String[] loadFileNames = mRes.getStringArray(R.array.load_file_names);
			for (String fileName : loadFileNames) {
				if (fileName.equals("")) continue;

				String dir = fileName.endsWith(".db") ? DATABASES_DIR : DATA_DIR;
				Load1Graphic(dir, fileName);
			}
		}

		private void Load1Graphic(String dir, String fileName){
			BufferedInputStream in = null;
			BufferedOutputStream out = null;

			File file = new File(getFilePath(dir, fileName));
			File parent = new File(file.getParent());
			if (!parent.exists()) { parent.mkdirs(); }
			if (!parent.exists()) {
				Log.w(LOGTAG, "Load1Graphic: can not create directory " + file.getPath());
				return;
			}

			byte[] bytes = new byte[8192];
			int count = 0;
			try {
				in = new BufferedInputStream(streamFromResource(dir, fileName), 8192);
				out = new BufferedOutputStream(new FileOutputStream(file));
				do {
					count = in.read(bytes, 0, 8192);
					if (count > 0) {
						out.write(bytes, 0, count);
						publishProgress(mProgressMarker);
					}
				} while (count != -1);
			} catch (Exception e) {
				Log.w(LOGTAG, "Load1Graphic: " + e);
			}
			closeStreams(in, out);
		}

		public void doFirstLoad(){
			// The first load is a short program of comments that will be displayed
			// by the Editor

			Editor.DisplayText="!!\n\n" +
					"Welcome to BASIC!\n\n" +
					"Press Menu->More->About\n" +
					"to get more information\n" +
					"about this release.\n\n" +
					"The BASIC! User's Manual,\n" +
					"De Re BASIC!, can also be\n" +
					"found at that location.\n\n" +
					"Press Menu->Clear to clear\n" +
					"this message and start\n" +
					"writing your own BASIC!\n" +
					"program.\n\n"+
					"!!"
					;  // Initialize the Display Program Lines
		}

		public void doCantLoad(){
			// A short program of comments that will be displayed
			// by the Editor to indicate the Base Drive is not writable

			Editor.DisplayText="!!\n\n" +
					"BASIC! is unable to write\n" +
					"its sample programs.\n" +
					"You can write and run\n" +
					"programs, but you cannot\n" +
					"save them. You can press\n" +
					"Menu->More->Preferences\n" +
					"and select \"Base Drive\"\n" +
					"to change the setting\n" +
					"to writable storage.\n\n" +
					"!!";
		}

		private void LoadTheProgram(){

			// Reads the program file from res/raw or assets/<AppPath>/source and puts it into memory
			AddProgramLine APL = new AddProgramLine();
			String name = mRes.getString(R.string.my_program);
			InputStream inputStream = null;
			try { inputStream = streamFromResource(SOURCE_DIR, name); }
			catch (Exception ex) {											// If not found, give up
				Log.e(LOGTAG, "LoadTheProgram - error getting stream from resource: " + ex);
				return;
			}

			BufferedReader buffreader = new BufferedReader(new InputStreamReader(inputStream));
			String line = "";
			int count = 0;

			try {
				while ((line = buffreader.readLine()) != null) {			// Read and write one line at a time
					APL.AddLine(line);										// add the line to memory
					++count;
					if (count >= 200) {										// Show progress every 200 lines.
						publishProgress(mProgressMarker);
						count = 0;
					}
				}
			} catch (IOException e) {
				Log.e(LOGTAG, "LoadTheProgram - error reading, about line " + count + ": " + e);
			} finally {
				if (buffreader != null) {
					try { buffreader.close(); }
					catch (IOException e) {}
				}
			}
		}
	} // class Background

	/************************************** utility classes **************************************/

	public static class ScreenColors {
		public int textColor;
		public int backgroundColor;
		public int lineColor;

		public ScreenColors() {
			// By default, color1 is solid black, color2 is solid white,
			// and color3 is a shade of blue that Paul originally chose for "WBL".
			// The programmer may define the colors any way she likes in res/values/setup.xml.
			Resources res = BasicContext.getResources();
			int black = res.getInteger(R.integer.color1);
			int white = res.getInteger(R.integer.color2);
			int blue  = res.getInteger(R.integer.color3);
			String colorSetting = Settings.getEditorColor(BasicContext);
			if (colorSetting.equals("BW")) {
				textColor = black;
				backgroundColor = white;
				lineColor = textColor;
			} else
			if (colorSetting.equals("WB")) {
				textColor = white;
				backgroundColor = black;
				lineColor = textColor;
			} else
			if (colorSetting.equals("WBL")) {
				textColor = white;
				backgroundColor = blue;
				lineColor = black;
			}
			lineColor &= 0x80ffffff;		// half alpha
		}
	} // class ScreenColors

	public static class ColoredTextAdapter extends ArrayAdapter<String> {
		private final Activity mActivity;
		private final ArrayList<String> mList;
		private Typeface mTypeface = null;
		public int textColor;
		public int backgroundColor;
		public int lineColor;

		public ColoredTextAdapter(Activity activity, ArrayList<String> alist, Typeface typeface) {
			this(activity, alist);
			mTypeface = typeface;
		}

		public ColoredTextAdapter(Activity activity, ArrayList<String> alist) {
			super(activity, Settings.getLOadapter(activity), alist);
			mActivity = activity;
			mList = alist;
			ScreenColors colors = new ScreenColors();
			textColor = colors.textColor;
			backgroundColor = colors.backgroundColor;
			lineColor = colors.lineColor;
		}

		@Override
		public View getView(int position, View convertView, ViewGroup parent) {

			View row = convertView;
			if (row == null) {
				LayoutInflater inflater = (LayoutInflater) mActivity.getLayoutInflater();
				row = inflater.inflate(Settings.getLOadapter(mActivity), null);
			}
			TextView text = (TextView) row.findViewById(R.id.the_text);
			text.setTextColor(textColor);
			text.setText(mList.get(position));
			if (mTypeface != null) { text.setTypeface(mTypeface); }
			text.setBackgroundColor(backgroundColor);

			return row;
		}
	} // class ColoredTextAdapter

	public static void toaster(Context context, CharSequence msg) {		// Tell the user "msg" via Toast
		if ((context == null) || (msg == null) || msg.equals("")) return;
		Toast toast = Toast.makeText(context, msg, Toast.LENGTH_SHORT);
		toast.setGravity(Gravity.TOP|Gravity.CENTER, 0, 50);			// default: short, high toast
		toast.show();
	}

}
